package com.valsong.sandbox.mock.context;

import com.alibaba.jvm.sandbox.api.ProcessController;
import com.alibaba.jvm.sandbox.api.listener.ext.Advice;
import com.alibaba.jvm.sandbox.api.listener.ext.AdviceListener;
import com.valsong.sandbox.mock.AdviceListenerProvider;
import com.valsong.sandbox.mock.Invoker;
import com.valsong.sandbox.mock.MockConfig;
import com.valsong.sandbox.mock.MockFilter;
import lombok.extern.slf4j.Slf4j;

import javax.inject.Inject;
import javax.inject.Named;
import java.lang.reflect.AccessibleObject;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.LockSupport;

import static com.valsong.sandbox.mock.constant.MockConstants.MOCK_METHOD;


/**
 * InternalBeforeAdviceListenerProvider
 *
 * @author Val Song
 */
@Slf4j
public class InternalBeforeAdviceListenerProvider implements AdviceListenerProvider {


    @Inject
    @Named("facadeMockFilter")
    private MockFilter facadeMockFilter;

    @Inject
    @Named("groovyFileInvoker")
    private Invoker groovyFileInvoker;

    @Override
    public AdviceListener newAdviceListener(MockConfig mockConfig) {

        return new AdviceListener() {

            @Override
            protected void before(Advice advice) throws Throwable {

                long start = System.nanoTime();

                String classPattern = mockConfig.getClassPattern();
                AccessibleObject method = advice.getBehavior().getTarget();

                try {

                    //  判断是否是压测流量
                    Boolean shouldMock = facadeMockFilter.shouldMock(advice, mockConfig);

                    log.info("classPattern:[{}] method:[{}] shouldMock:[{}]", classPattern, method, shouldMock);

                    if (!shouldMock) {
                        return;
                    }

                } catch (Throwable throwable) {
                    log.error(throwable.getMessage(), throwable);
                    //如果mock过滤器发生异常则直接不走mock逻辑
                    ProcessController.throwsImmediately(throwable);
                }

                String mockInvoker = mockConfig.getMockInvoker();

                String mockInvokeMethod = mockConfig.getMockInvokeMethod();

                //如果mockInvokeMethod没有指定，则使用mock
                if (mockInvokeMethod == null || mockInvokeMethod.trim().isEmpty()) {
                    mockInvokeMethod = MOCK_METHOD;
                }

                //设置的执行时间 (纳秒)
                long minExecutionTime = Optional.ofNullable(mockConfig)
                        .map(MockConfig::getMinExecutionTime)
                        .filter(Objects::nonNull)
                        .map(time -> time * 1000 * 1000)
                        .orElse(0L);


                long maxExecutionTime = Optional.ofNullable(mockConfig)
                        .map(MockConfig::getMaxExecutionTime)
                        .filter(Objects::nonNull)
                        .map(time -> time * 1000 * 1000)
                        .orElse(0L);

                long executionTime;
                if (minExecutionTime > 0L && maxExecutionTime < minExecutionTime) {
                    executionTime = minExecutionTime;
                } else if (minExecutionTime <= 0L && maxExecutionTime > 0L) {
                    executionTime = maxExecutionTime;
                } else if (minExecutionTime > 0L && maxExecutionTime > minExecutionTime) {
                    executionTime = ThreadLocalRandom.current().nextLong(minExecutionTime, maxExecutionTime + 1);
                } else {
                    executionTime = 0L;
                }

                Object mockResult = null;

                try {
                    //执行mock方法
                    mockResult = groovyFileInvoker.invoke(mockInvoker, mockInvokeMethod, advice);

                } catch (Throwable throwable) {
                    log.error(throwable.getMessage(), throwable);
                    //mock执行出现异常则直接抛出
                    ProcessController.throwsImmediately(throwable);
                }

                //实际执行时间 (纳秒)
                long actualExecutionTime = System.nanoTime() - start;

                //暂停时间  (纳秒)
                long parkTime = executionTime - actualExecutionTime;

                if (parkTime > 0L) {
                    LockSupport.parkNanos(parkTime);
                }

                //总共执行时间 (纳秒）
                long totalExecutionTime = System.nanoTime() - start;

                long actualCost = actualExecutionTime / 1000000;

                long totalCost = totalExecutionTime / 1000000;

                log.info("Mock complete! actualCost:[{}]ms totalCost:[{}]ms classPattern:[{}] method:[{}] mockResult:[{}] ",
                        actualCost, totalCost, classPattern, method, mockResult);

                //返回mock结果
                ProcessController.returnImmediately(mockResult);

            }

        };
    }
}
